#!/usr/bin/env python
# encoding: utf-8

from common.libs.Log import logger
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtPrintSupport import QPrinter, QPrintDialog, QPrintPreviewDialog
from PyQt5.QtMultimedia import QSound
from PIL import Image
import xlwt
import xlrd
import xlsxwriter
from threading import Thread
from threading import Timer
from queue import Queue

import PyQt5_stylesheets
# from images import images_rc
import os
import glob
import re
import win32api
import sys

import random
from decimal import Decimal
import time
import serial
import serial.tools.list_ports
import binascii
import qtawesome as qta
from six import unichr
import numpy as np

import pyqtgraph as pg

pg.setConfigOption('background', '#E3E6E3')
pg.setConfigOption('foreground', 'k')
import pyqtgraph.opengl as gl
from pyqtgraph.opengl import GLViewWidget
from pyqtgraph import GraphicsLayoutWidget

import threading
import numpy as np
import json

BOOT_DIR = os.getcwd()
import configparser

# 打开调试
config = configparser.ConfigParser()
config.read(os.getcwd()+'/conf/base.ini')

DEBUG = True
# DEBUG = False

# auto = config['base']['task_auto']

# CYCLE_TEST = False
# if auto == 'True':
    # CYCLE_TEST = True
# if auto == 'False':
    # CYCLE_TEST = False

task_stop_err = int(config['base']['task_stop_err'])
task_show = config['base']['task_show']

# task_stop_err = 5000
# 1mm走位需要的脉冲数
# AXIS_MULTIPLE = int(config['base']['pulse'])
AXIS_MULTIPLE = 100

from Help import Help
from Help import WATER_CMD
# 监督通道突变差阈值


data_start = 0

# 界面导入
from pwdInput import PwdInput
from ComSetWidget import ComSetWidget
from BaseSetWidget import BaseSetWidget
from envMeasureWidget import EnvMeasureWidget
from AddPddDialog import PddDialog
from AddOarDialog import OarDialog
from AddScanDialog import ScanDialog, ScanResultWidget
#  from DeviceSelectDialog import DeviceInputDlg
from RayGraphWidget import RayCanvasWidget
from RepeatMeasure import RepeatMeasureDialog
# from MotorControl import MotorControlDock
from MotorControl import MotorControlWidget
from MotorControl import MoveShowDock
# from ChartDeal import ChartDealWidget
# 界面导入
from ProjectManager import ProjectDock
from ProjectManager import ProjectCreateDialog
from ProjectManager import TaskRunManager
from fileChangeWidget import FileChangeForm


class GraphWidget(GraphicsLayoutWidget):
    def __init__(self):
        super(GraphWidget, self).__init__()
        # 右上角显示
        self.label = pg.LabelItem(justify='right')
        self.addItem(self.label, 0, 1, 1, 1)
        self.pg = pg
        # 曲线添加
        self.plot = self.pg.PlotItem()
        self.plot = self.addPlot(1, 1, colspan=2)
        self.plot.setYRange(0, 100)

        self.data = np.random.normal(size=10)
        self.curve_master_data = [100, 100]
        self.curve_slave_data = [0, 0]
        self.curve_data_x = [0, 300]
        # 曲线设置
        self.plot.setAutoVisible(y=True)
        self.plot.setTitle('通道数据实时曲线图')
        self.plot.setLabel('left', "相对剂量(%)", units='')
        self.plot.setLabel('bottom', "距离(mm)", units='')
        self.plot.addLegend()
        # self.plot.setRange(xRange=[0,300],yRange=[0,110],padding=0)
        # self.plot.setLimits(xMin=0,xMax=300,yMin=0,yMax=110)
        # 主测通道和监测通道
        self.curve_master = self.plot.plot(self.curve_data_x, self.curve_master_data, name='主测通道',
                                           pen=self.pg.mkPen('r', width=2))
        self.curve_slave = self.plot.plot(self.curve_data_x, self.curve_slave_data, name='监测通道',
                                          pen=self.pg.mkPen('g', width=2))
        # 网格显示
        self.plot.showGrid(x=True, y=True, alpha=0.7)
        # 鼠标坐标显示文本
        self.text_pos = self.pg.TextItem("(0,0)", anchor=(0.5, -1.0), color='k')
        self.plot.addItem(self.text_pos)
        self.text_pos.setPos(0, 0)
        # 鼠标十字移动
        self.vLine = self.pg.InfiniteLine(pen=self.pg.mkPen('b', width=1), angle=90, movable=False)
        self.hLine = self.pg.InfiniteLine(pen=self.pg.mkPen('b', width=1), angle=0, movable=False)
        self.plot.addItem(self.vLine, ignoreBounds=True)
        self.plot.addItem(self.hLine, ignoreBounds=True)
        # 鼠标移动绑定
        self.vb = self.plot.vb
        self.proxy = self.pg.SignalProxy(self.plot.scene().sigMouseMoved, rateLimit=60, slot=self.mouseMoved)
        self.plot.autoRange()

    def find_nearest(self, array, value):
        idx = (np.abs(array - value)).argmin()
        return array[idx]

    def mouseMoved(self, evt):
        tx = np.array(self.curve_data_x)
        pos = evt[0]
        if self.plot.sceneBoundingRect().contains(pos):
            mousePoint = self.vb.mapSceneToView(pos)
            self.vLine.setPos(mousePoint.x())
            self.hLine.setPos(mousePoint.y())
            self.setCursor(Qt.BlankCursor)
            self.text_pos.setText("(%0.1f,%0.1f)" % (mousePoint.x(), mousePoint.y()))
            self.text_pos.setPos(mousePoint.x(), mousePoint.y())
            # self.label.setText("<span style='font-size: 12pt'>x=%0.1f,  <span style='color: red'>y=%0.1f</span>" % (mousePoint.x(), mousePoint.y()))
            # index = int(mousePoint.x())
            # try:
            # n = self.find_nearest(tx,index)
            # index = np.where(tx==n)[0][0]
            # self.label.setText("<span style='font-size: 12pt;color: blue'>通道实时数据:  </span>\
            # <span style='font-size: 12pt'>x=\t{:20},  \
            # <span style='color: red'>y1=\t{:28}</span> ,\
            # <span style='color: green'>y2=\t{:32}</span>"\
            # .format('%0.1f'%mousePoint.x(), '%0.1f'%self.curve_master_data[index],'%0.1f'%self.curve_slave_data[index]))
            # self.vLine.setPos(mousePoint.x())
            # self.hLine.setPos(mousePoint.y())
            # except:
            # pass

    def drawLine(self, x, y):
        self.curve_master.setData(x=self.curve_data_x, y=self.curve_master_data)
        self.curve_slave.setData(x=self.curve_data_x, y=self.curve_slave_data)

    def draw_two(self, x, master, slave):
        if master and slave:
            if max(master) == 0 or max(slave) == 0:
                master = [m * 100 / (max(master) + 0.000001) for m in master]
                slave = [m * 100 / (max(slave) + 0.000001) for m in slave]
            else:
                master = [m * 100 / max(master) for m in master]
                slave = [m * 100 / max(slave) for m in slave]
            self.curve_master.setData(x=x, y=master)
            self.curve_slave.setData(x=x, y=slave)
        else:
            self.curve_master.setData(x=[], y=[])
            self.curve_slave.setData(x=[], y=[])
        # self.plot.setXRange(0,300)
        # self.plot.setYRange(0,120)

    def clear_data(self):
        self.curve_data_x = []
        self.curve_master_data = []
        self.curve_slave_data = []
        self.curve_master.setData(x=self.curve_data_x, y=self.curve_master_data)
        self.curve_slave.setData(x=self.curve_data_x, y=self.curve_slave_data)

    def drawLineAppend(self):
        self.curve_master.setData(self.curve_master_data)
        self.curve_slave.setData(self.curve_slave_data)

    def tunnelZero(self):
        self.curve_data_x = []
        self.curve_slave_data = []
        self.curve_master_data = []
        self.drawLine()


class DataTabView(QTableView):
    def __init__(self, parent=None):
        '''表格初始化'''
        super(DataTabView, self).__init__(parent)
        self.model = QStandardItemModel(0, 0)
        #  self.HeaderList = ['x(mm)', 'y(mm)', 'z(mm)', '主测通道(mGy)', '监测通道(mGy)', '修正', '归一化']
        self.HeaderList = ['x(mm)', 'y(mm)', 'z(mm)', '主测通道(cGy/min)', '监测通道(cGy/min)']
        self.model.setHorizontalHeaderLabels(self.HeaderList)  #
        self.setModel(self.model)
        # 下面代码让表格100填满窗口
        self.horizontalHeader().setStretchLastSection(True)
        self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.isClearChooseLine_Flag = False
        #  self.setMaximumHeight(250)

        self.clear_data()
        # 右键菜单设置
        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.right_menu)

    def right_menu(self):
        self.menu = QMenu()
        exportAction = QAction("&导出excel", triggered=self.exportToExcel)
        self.menu.addAction(exportAction)
        self.menu.exec(QCursor.pos())

    def exportToExcel(self):
        f = QFileDialog.getSaveFileName(self, "导出Excel", '%s/data' % os.getcwd(), 'Excel Files(*.xls)')
        if f[0]:
            try:
                filename = f[0]
                wb = xlwt.Workbook()
                ws = wb.add_sheet('sheet1')  # sheetname
                data = []
                data.append(self.HeaderList)
                row = self.model.rowCount()
                for r in range(0, row):
                    x = self.model.data(self.model.index(r, 0))
                    y = self.model.data(self.model.index(r, 1))
                    z = self.model.data(self.model.index(r, 2))
                    t1 = self.model.data(self.model.index(r, 3))
                    t2 = self.model.data(self.model.index(r, 4))
                    if x and y and z and t1 and t2:
                        data = []
                        for r in range(0,row):
                            data.append([float(x), float(y), float(z), float(t1), float(t2)])
                        if data:
                            for i in range(0, len(data)):
                                for j in range(0, len(data[i])):
                                    ws.write(i, j, data[i][j])
                            wb.save(filename)
                            QMessageBox.warning(self, '提示', '导出成功!\r\n%s' % filename, QMessageBox.Yes)
                            return
                QMessageBox.warning(self, '警告', '导出失败!', QMessageBox.Yes)
            except:
                QMessageBox.warning(self, '警告', '无法导出!', QMessageBox.Yes)

    def clear_data(self):
        self.model.clear()
        self.model = QStandardItemModel(0, 0)
        self.model.setHorizontalHeaderLabels(self.HeaderList)  #
        self.setModel(self.model)

    def add_data(self, x='', y='', z='', t1='', t2=''):
        rowNum = self.model.rowCount()  # 总行数
        self.model.setItem(rowNum, 0, QStandardItem(str(x)))
        self.model.setItem(rowNum, 1, QStandardItem(str(y)))
        self.model.setItem(rowNum, 2, QStandardItem(str(z)))
        self.model.setItem(rowNum, 3, QStandardItem(str('%.2f'%t1)))
        self.model.setItem(rowNum, 4, QStandardItem(str('%.2f'%t2)))

    def insert_data(self, rowNum, x='', y='', z='', t1='', t2=''):
        # rowNum = self.model.rowCount()  # 总行数
        self.model.setItem(rowNum, 0, QStandardItem(str(x)))
        self.model.setItem(rowNum, 1, QStandardItem(str(y)))
        self.model.setItem(rowNum, 2, QStandardItem(str(z)))
        self.model.setItem(rowNum, 3, QStandardItem(str('%.2f'%t1)))
        self.model.setItem(rowNum, 4, QStandardItem(str('%.2f'%t2)))

    def get_standard(self, x, ave):
        import math
        n = len(x)
        s = 0
        for i in range(0, n):
            s = s + pow((int(x[i]) - ave), 2)
        s = math.sqrt(1 / (n - 1) * s)
        return s

    def get_data(self):
        rowNum = self.model.rowCount()  # 总行数
        x = [self.model.data(self.model.index(i, 1)) for i in range(0, rowNum)]
        line1 = [self.model.data(self.model.index(i, 4)) for i in range(0, rowNum)]
        line2 = [self.model.data(self.model.index(i, 5)) for i in range(0, rowNum)]
        return (x, line1, line2)

    def get_all_data(self):
        rowNum = self.model.rowCount()  # 总行数
        colNum = self.model.columnCount()  # 总行数
        data = []
        for j in range(0, colNum):
            col = [self.model.data(self.model.index(i, j)) for i in range(0, rowNum)]
            data.append(col)
        return data


from ui.main import Ui_MainWindow
# from chartAnalyse import chartAnalyseForm
from ui import images
import winsound


class MainWindow(QMainWindow, Ui_MainWindow):
    '''三维水箱主程序'''
    signalOpengl = pyqtSignal(float, float, float)
    signalOpenglLoadConfig = pyqtSignal(dict)
    signalTunnel = pyqtSignal(list, list, list)
    signalBeep = pyqtSignal(int, int)
    signalMsg = pyqtSignal(str, str)
    signalTimer = pyqtSignal()

    progress_value = 0
    # 移动距离
    err_x = 0
    err_y = 0
    err_z = 0
    # 真实坐标
    real_x = 0
    real_y = 0
    real_z = 0
    # 前一刻坐标
    pre_x = 0
    pre_y = 0
    pre_z = 0
    pulse_x = 0
    pulse_y = 0
    pulse_z = 0
    #  重复性测量标志
    repeat_measure_flag = False
    #  本底测量标志
    env_measure_flag = False
    #  移动标志
    move_flag = False
    task_flag = False
    taskmove_flag = False
    task_cnt = 0
    task_current = None
    task = None
    # 意外停止标志
    stop_flag = False
    # 任务第一个点移动显示标志
    task_move_first_flag = True
    pddoarList = []
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)
        # 界面尺寸最大化
        self.setWindowState(Qt.WindowMaximized)
        self.setWindowIcon(QIcon(':icons/images_rc/app.png'))
        self.setWindowTitle("三维射束分析仪软件-RG202")
        # 串口监测定时器
        self.serial = serial.Serial()
        self.signalMsg.connect(self.MessageBox)
        self.signalTimer.connect(self.Timer)

        # 密码输入界面
        self.pwdInputDlg = PwdInput()
        self.pwdInputDlg.setWindowIcon(QIcon(':icons/images_rc/com.png'))

        # 串口通信设置
        self.comSetWidget = ComSetWidget()
        self.comSetWidget.setWindowIcon(QIcon(':icons/images_rc/com.png'))
        # flash基本设置
        self.baseSetWidget = BaseSetWidget(None, self.serial)
        self.baseSetWidget.setWindowIcon(QIcon(':icons/images_rc/move.png'))
        self.baseSetWidget.signal_send.connect(self.cmd_send)
        # 本底测量
        self.envMeasureWidget= EnvMeasureWidget(None, self.serial)
        self.envMeasureWidget.setWindowIcon(QIcon(':icons/images_rc/box.png'))
        # 重复性测量
        self.repeatMeasureDlg = RepeatMeasureDialog(None, self.serial)
        self.repeatMeasureDlg.setWindowIcon(QIcon(':icons/images_rc/repeat.png'))
        # 曲线处理
        # self.chartDealWidget = ChartDealWidget()
        # self.horizontalLayout_chartDeal.addWidget(self.chartDealWidget)
        # 电机移动示意
        self.move_dock = MoveShowDock(self)
        self.move_dock.signal_movePos.connect(self.motorMoveAxis_position)
        # 电机控制窗口
        self.motorControlWidget = MotorControlWidget(None, self.serial)
        self.motorControlWidget.signal_movePos.connect(self.motorMoveAxis_position)
        self.motorControlWidget.signal_setLimit.connect(self.setLimit)
        self.motorControlWidget.pushButton_backZero.clicked.connect(self.cmd_backZero)
        self.motorControlWidget.pushButton_setZero.clicked.connect(self.cmd_ResetAxis)
        # 项目窗口
        self.proj_create_dlg = ProjectCreateDialog()
        self.proj_dock = ProjectDock(self)
        self.proj_dock.treeWidget.signalClickTask.connect(self.move_dock.rayGL.load_config)
        # 添加dock窗口到主窗口中
        self.addDockWidget(Qt.LeftDockWidgetArea, self.proj_dock)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.move_dock)
        # 曲线显示窗口
        self.graph = GraphWidget()
        self.dataTab = DataTabView()
        self.verticalLayout_chart.addWidget(self.graph)
        self.verticalLayout_data_table.addWidget(self.dataTab)
        # 曲线分析
        # self.chartAnalyse = chartAnalyseForm()

        # 坐标3d显示信号
        self.signalOpengl.connect(self.updatePos)
        # 曲线显示信号绑定
        self.signalTunnel.connect(self.graph.draw_two)
        self.signalBeep.connect(self.beepSound)

        self.serial_init()
        # 工具栏和菜单
        self.createMenus()

    def createMenus(self):
        # 文件
        self.newProjectAct = QAction(QIcon(':icons/images_rc/file.png'),"&新建项目", self, shortcut="Ctrl+N", triggered=self.proj_dock.treeWidget.newProject)
        self.openProjectAct = QAction(QIcon(':icons/images_rc/open.png'), "&打开项目", self, shortcut="Ctrl+O", triggered=self.proj_dock.treeWidget.openProject)
        self.closeProjectAct = QAction(QIcon(':icons/images_rc/close.png'), "&关闭项目", self, shortcut="Ctrl+C", triggered=self.proj_dock.treeWidget.closeProject)
        self.fileChangeAct = QAction(QIcon(':icons/images_rc/file.png'), "&TPS文件转换", self, triggered=self.TpsFileChange)
        self.exitAct = QAction(QIcon(':icons/images_rc/exit.png'), "&退出", self, shortcut="Ctrl+Q", statusTip="关闭程序", triggered=self.close)
        # self.printAct = QAction(QIcon(':icons/images_rc/printer.png'), "&打印", self, shortcut="Ctrl+P", triggered=self.chartAnalyse.on_printAction1_triggered)
        # self.printPreviewAct = QAction(QIcon(':icons/images_rc/view.png'), "&打印预览", triggered=self.chartAnalyse.on_printAction2_triggered)
        self.fileMenu = self.menuBar().addMenu("&文件")
        self.fileMenu.addAction(self.newProjectAct)
        self.fileMenu.addAction(self.openProjectAct)
        self.fileMenu.addAction(self.closeProjectAct)
        self.fileMenu.addAction(self.fileChangeAct)
        # 最近文件打开
        # self.recentProjectMenu = self.fileMenu.addMenu(QIcon(':icons/images_rc/project.png'), "&最近项目")
        config_path = os.getcwd() + '/conf/project.ini'
        config = configparser.ConfigParser()
        config.read(config_path)
        recent = dict(config['recent'])
        recent_list = [recent[k] for k in recent.keys()]
        self.recentProjectActions = []
        for s in recent_list:
            Action = QAction(QIcon(':icons/images_rc/project.png'), "&%s" % s, self,
                             triggered=self.proj_dock.treeWidget.openRecentProject)
            self.recentProjectActions.append(Action)
        # self.recentProjectMenu.addActions(self.recentProjectActions)
        self.fileMenu.addSeparator()
        self.fileMenu.addAction(self.exitAct)

        # 视图
        self.addPddTaskAct = QAction(QIcon(':icons/images_rc/pdd.png'),"&添加PDD任务", triggered=self.proj_dock.treeWidget.addPddTask)
        self.addOarTaskAct = QAction(QIcon(':icons/images_rc/oar.png'),"&添加OAR任务", triggered=self.proj_dock.treeWidget.addOarTask)
        self.projDockShowAct = QAction(QIcon(':icons/images_rc/project.png'), "&项目管理窗口", triggered=self.proj_dock.show)
        self.moveDockShowAct = QAction(QIcon(':icons/images_rc/motor.png'), "&移动显示窗口", triggered=self.move_dock.show)
        self.viewMenu = self.menuBar().addMenu("&视图")
        self.viewMenu.addAction(self.addPddTaskAct)
        self.viewMenu.addAction(self.addOarTaskAct)
        self.viewMenu.addSeparator()
        self.viewMenu.addAction(self.projDockShowAct)
        self.viewMenu.addAction(self.moveDockShowAct)

        # 设置
        # self.comSetAct = QAction(QIcon(':icons/images_rc/com.png'), "&通信串口设置", self, triggered=self.comSetWidget.show)
        self.comSetAct = QAction("&通信串口", self, triggered=self.comSetWidget.show)
        # self.baseSetAct = QAction(QIcon(':icons/images_rc/move.png'),"&移动和测量设置", self, triggered=self.baseSetWidget.exec)
        self.baseSetAct = QAction(QIcon(':icons/images_rc/move.png'),"&移动和测量设置", self, triggered=self.pwdInput)
        self.settingMenu = self.menuBar().addMenu("&设置")
        self.settingMenu.addAction(self.comSetAct)
        self.settingMenu.addAction(self.baseSetAct)
        self.skinMenu = self.settingMenu.addMenu("&界面")
        self.skinDefaultAct = QAction('&经典(默认)', self, triggered=lambda:self.skinChanged('default'))
        self.skinBlackAct = QAction('&黑色', self, triggered=lambda:self.skinChanged('dark'))
        self.skinBlueAct = QAction('&天蓝色', self, triggered=lambda:self.skinChanged('blue'))
        self.skinMenu.addAction(self.skinDefaultAct)
        self.skinMenu.addAction(self.skinBlackAct)
        self.skinMenu.addAction(self.skinBlueAct)

        # 读取信息
        self.readInfoAct = QAction(QIcon(':icons/images_rc/info.png'),'&读取信息', self, triggered=self.cmd_ReadInfo)
        # 操作
        self.envAct = QAction(QIcon(':icons/images_rc/box.png'),'&本底测量', self, triggered=self.envMeasureWidget.exec)
        self.repeatMeasureAct = QAction(QIcon(':icons/images_rc/repeat.png'),'&重复性', self, triggered=self.repeatMeasureDlg.exec)
        self.motorControlAct = QAction(QIcon(':icons/images_rc/motor.png'), "&电机操作", triggered=self.motorControlWidget.exec)
        self.operateMenu = self.menuBar().addMenu("&操作")
        self.operateMenu.addAction(self.envAct)
        self.operateMenu.addAction(self.repeatMeasureAct)
        self.operateMenu.addAction(self.motorControlAct)

        # 其他菜单
        self.otherMenu = self.menuBar().addMenu("&其他")
        self.openDocMenu = self.otherMenu.addMenu(QIcon(':icons/images_rc/file.png'), "&相关文档")
        self.doc_path = os.getcwd() + "/doc"
        doc_list=os.listdir(self.doc_path)
        self.openDocActions = []
        for s in doc_list:
            icon = QIcon(':icons/images_rc/file.png')
            if s.endswith('.xls') or s.endswith('.xlsx'):
                icon = QIcon(':icons/images_rc/excel.png')
            if s.endswith('.doc') or s.endswith('.docx'):
                icon = QIcon(':icons/images_rc/word.png')
            if s.endswith('.pdf'):
                icon = QIcon(':icons/images_rc/pdf.png')
            Action = QAction(icon, "&%s" % s, self, triggered=self.openDoc)
            self.openDocActions.append(Action)
        self.openDocMenu.addActions(self.openDocActions)
        # 串口状态
        self.comboBox_port = QComboBox()
        self.qta_com_status = QLabel()
        self.qta_com_status.setStyleSheet('color: red;')
        self.qta_com_status.setFont(qta.font('fa', 32))
        self.qta_com_status.setText(unichr(0xf111))

        # 任务执行
        self.startTaskAct = QAction(QIcon(':icons/images_rc/start.png'),'&开始执行', self, triggered=self.startMove)
        self.stopTaskAct = QAction(QIcon(':icons/images_rc/stop.png'),'&暂停任务', self, triggered=self.pauseMove)
        # self.stopMoveAct = QAction('&停止移动', self, triggered=self.motorControlWidget.moveStop)
        # 曲线显示
        self.chartAnalyseAct = QAction(QIcon(':icons/images_rc/chart.png'),'&单曲线分析', self, triggered=self.proj_dock.treeWidget.showChart)
        self.multiChartAnalyseAct = QAction(QIcon(':icons/images_rc/multi-chart.png'),'&多曲线分析', self, triggered=self.proj_dock.treeWidget.multiChartShow)

        self.label_comStatus= QLabel()
        self.label_comStatus.setMinimumWidth(32)
        self.label_comStatus.setStyleSheet('image: url(:icons/images_rc/com-offline.png)')
        self.toolbar = self.addToolBar('工具栏')
        # self.toolbar.addWidget(self.qta_com_status)
        self.toolbar.addWidget(self.label_comStatus)
        self.toolbar.addAction(self.comSetAct)
        # self.toolbar.addWidget(QLabel('串口通信'))
        self.toolbar.addAction(self.readInfoAct)
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.envAct)
        self.toolbar.addAction(self.repeatMeasureAct)
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.newProjectAct)
        self.toolbar.addAction(self.openProjectAct)
        self.toolbar.addAction(self.closeProjectAct)
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.addPddTaskAct)
        self.toolbar.addAction(self.addOarTaskAct)
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.startTaskAct)
        self.toolbar.addAction(self.stopTaskAct)
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.chartAnalyseAct)
        self.toolbar.addAction(self.multiChartAnalyseAct)

        self.toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)

        self.actionDisable(['env','repeat','start','stop'])

    def pwdInput(self):
        dlg = PwdInput()
        if dlg.exec():
            pwd = dlg.pwd
            if dlg.pwd == 'fushe01020304':
                self.baseSetWidget.exec()
            else:
                QMessageBox.information(self, '提示','密码错误', QMessageBox.Yes)

    def TpsFileChange(self):
        # 文件转换
        self.fileChangeForm= None
        self.fileChangeForm= FileChangeForm()
        self.fileChangeForm.show()

    def actionEnabled(self,action):
        if 'env' in action:
            self.envAct.setEnabled(True)
        if 'com' in action:
            self.comSetAct.setEnabled(True)
        if 'repeat' in action:
            self.repeatMeasureAct.setEnabled(True)
        if 'start' in action:
            self.startTaskAct.setEnabled(True)
        if 'stop' in action:
            self.stopTaskAct.setEnabled(True)

    def actionDisable(self, action):
        if 'env' in action:
            self.envAct.setEnabled(False)
        if 'com' in action:
            self.comSetAct.setEnabled(False)
        if 'repeat' in action:
            self.repeatMeasureAct.setEnabled(False)
        if 'start' in action:
            self.startTaskAct.setEnabled(False)
        if 'stop' in action:
            self.stopTaskAct.setEnabled(False)

    def skinChanged(self, name='default'):
        if name == "default":
            self.setStyleSheet('None')
        if name == "dark":
            self.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_Dark"))
            self.proj_dock.treeWidget.pddDlg.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_Dark"))
            self.proj_dock.treeWidget.oarDlg.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_Dark"))
            self.proj_dock.treeWidget.createProjectDlg.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_Dark"))
            self.repeatMeasureDlg.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_Dark"))
            self.motorControlWidget.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_Dark"))
            self.comSetWidget.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_Dark"))
            self.baseSetWidget.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_Dark"))
            self.envMeasureWidget.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_Dark"))
        if name == "blue":
            self.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_blue"))
            self.proj_dock.treeWidget.pddDlg.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_blue"))
            self.proj_dock.treeWidget.oarDlg.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_blue"))
            self.proj_dock.treeWidget.createProjectDlg.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_blue"))
            self.repeatMeasureDlg.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_blue"))
            self.motorControlWidget.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_blue"))
            self.comSetWidget.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_blue"))
            self.baseSetWidget.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_blue"))
            self.envMeasureWidget.setStyleSheet(PyQt5_stylesheets.load_stylesheet_pyqt5(style="style_blue"))

    def openDoc(self):
        name = self.sender().text().replace('&','')
        import win32api
        import win32con
        from win32comext.shell.shell import ShellExecuteEx
        from win32comext.shell import shellcon
        helplFile = r'"C:\\Program Files (x86)\\WPS Office\\wps.exe"'
        try:
            # 文件路径加双引号，防止路径中存在空格
            FileName=r'"%s\doc\%s"'%(os.getcwd(), name)
            procInfo = ShellExecuteEx(nShow=win32con.SW_SHOWNORMAL,
                                  fMask=shellcon.SEE_MASK_NOCLOSEPROCESS,
                                  lpVerb='open',
                                  lpFile=helplFile,
                                  lpParameters=FileName)
        except:
            QMessageBox.warning(self, '错误' , '请安装WPS,并确保其安装路径在: %s'%helplFile , QMessageBox.Yes)

    def updatePos(self, x, y, z):
        self.motorControlWidget.updatePos(x, y, z)
        self.move_dock.updatePos(x, y, z)

    def beepSound(self, fps, _time):
        t = Timer(0.001, winsound.Beep, args=(fps, _time))
        t.start()

    def serial_init(self):
        '''初始化串口'''
        self.serial.timeout = 0.5  # make sure that the alive event can be checked from time to time
        self.thread = None
        self.serial_find_flag = False
        self.serial_alive = False
        self.serial.port = None
        self.serial.baudrate = '115200'
        self.serial.bytesize = 8
        self.serial.parity = 'N'
        self.serial.stopbits = 1
        self.serial.dtr = True
        self.serial.rts = True
        self.recv = ''

        self.portList = []
        # 串口查询定时器
        self.thread_find_open = threading.Thread(target=self.FindOpenCom)
        self.thread_find_open.setDaemon(True)
        self.thread_find_open.start()

    def Timer(self):
        self.timer_recv = QTimer()
        self.timer_recv.timeout.connect(self.RecvCom)
        self.timer_recv.start(50)
        # 状态变化
        self.qta_com_status.setStyleSheet('color: green;')
        self.label_comStatus.setStyleSheet('image: url(:icons/images_rc/com.png)')
        # 读取信息
        self.cmd_ReadInfo()
        # self.actionEnabled(['start','env','repeat'])
        # 任务执行初始化
        self.taskRunInit()

    def FindOpenCom(self):
        '''搜索串口并打开串口'''
        while not self.serial_alive:
            config = configparser.ConfigParser()
            config.read(os.getcwd() + '/conf/com.ini')
            port_list = serial.tools.list_ports.comports()
            port = ''
            baudrate = ''
            for p in port_list:
                if p.serial_number == config['com']['serial_number']:
                    port = p.device
                    baudrate = config['com']['baudrate']
                    break
            if port and baudrate:
                self.serial_find_flag = False
                self.serial.port = port
                self.serial.baudrate = baudrate
                try:
                    self.serial.open()
                except serial.SerialException as e:
                    print(e)
                    # self.signalMsg.emit('串口打开错误', '错误原因：\n %s' % e)
                    # QMessageBox.information(self, '串口打开错误', '错误原因：\n %s' % e, QMessageBox.Yes)
                else:
                    self.serial_alive = True
                    self.signalTimer.emit()

    def RecvCom(self):
        """ 串口接收 """
        if self.serial_alive:
            try:
                n = self.serial.inWaiting()
                if n:
                    b = self.serial.read(n)
                # b = self.serial.read(255)
                # if b:
                    # 将byte转换为十六进制字符串
                    data = str(binascii.b2a_hex(b).decode('utf-8'))
                    self.recv += data.upper()
                    logger.info('接收命令:')
                    logger.info(' '.join([self.recv[i * 2:i * 2 + 2] for i in range(0, int(len(self.recv) / 2))]))
                    #  if DEBUG:
                        #  print('接收命令：', ' '.join([self.recv[i * 2:i * 2 + 2] for i in range(0, int(len(self.recv) / 2))]))
                    try:
                        self.cmd_deal()
                    except Exception as e:
                        print(e)
            except:
                # 串口异常, 关闭串口
                if self.serial.isOpen():
                    self.serial_alive = False
                    self.serial.close()
                    # self.qta_com_status.setStyleSheet('color: red;')
                    self.label_comStatus.setStyleSheet('image: url(:icons/images_rc/com-offline.png)')
                    self.startTaskAct.setEnabled(False)
                    self.stopTaskAct.setEnabled(False)
                    self.repeatMeasureAct.setEnabled(False)
                    self.envAct.setEnabled(False)
                    # self.timer_recv.stop()
                    # 打开串口查询线程
                    self.thread_find_open = threading.Thread(target=self.FindOpenCom)
                    self.thread_find_open.setDaemon(True)
                    self.thread_find_open.start()

    def WriteCom(self, cmd):
        '''串口写入,十六进制格式字符串'''
        if self.serial_alive:
            if self.serial.isOpen():
                data = binascii.unhexlify(cmd)
                logger.info('发送命令:')
                logger.info(' '.join([cmd[i * 2:i * 2 + 2] for i in range(0, int(len(cmd) / 2))]))
                #  if DEBUG:
                    #  print('发送命令：', ' '.join([cmd[i * 2:i * 2 + 2] for i in range(0, int(len(cmd) / 2))]))
                self.serial.write(data)
        else:
            QMessageBox.information(self, '通信提示', '通信异常,请检查通信接口', QMessageBox.Yes)

    def cmd_send(self, cmd_list):
        '''发送命令'''
        if DEBUG:
            print((cmd_list))
        cmd = ''.join(cmd_list)
        cmd.upper()
        cmd_start = cmd_list[0]
        cmd_len = hex(int(len(cmd) / 2)).replace('0x', '').zfill(4).upper()
        cmd_list[1] = cmd_len
        if len(cmd_len) == 4:
            hex_len = int(cmd_len[:2], 16) + int(cmd_len[-2:], 16)
            cmd_sum = (hex(int(cmd_start, 16) + int(cmd_len[:2], 16) + int(cmd_len[-2:], 16))).upper()[-2:]
        if len(cmd_len) == 2:
            hex_len = int(cmd_len, 16)
            cmd_sum = (hex(int(cmd_start, 16) + int(cmd_len, 16))).upper()[-2:]
        # print(cmd_sum)
        cmd_list[2] = cmd_sum
        s = 0
        s = int(cmd_list[0], 16) + hex_len + int(cmd_list[2], 16) + int(cmd_list[3], 16)
        for i in range(0, len(cmd_list[4])):
            if i % 2 == 0:
                s = s + int(cmd_list[4][i:i + 2], 16)
        s = hex(s).upper()[-2:]
        cmd_list[-1] = s
        send = ''.join(cmd_list)
        self.WriteCom(send)

    def cmd_deal(self):
        '''串口处理命令，监测校验和是否正确'''
        self.recv = self.recv
        start = self.recv.find("AA")
        startwith = self.recv.startswith('AA')
        def find_last(string, s):
            last_position = -1
            while True:
                position = string.find(s, last_position + 1)
                if position == -1:
                    return last_position
                last_position = position
        start_num = len(re.compile('.*?(AA).*?').findall(self.recv))
        if self.recv.startswith('AA'):
            for i in re.compile('.*?(AA).*?').findall(self.recv):
                if len(self.recv) < 6:
                    return
                theory_len = int(self.recv[2:4], 16)
                real_len = len(self.recv) / 2
                if theory_len <= real_len:
                    cmd = self.recv[start:theory_len * 2]
                    self.recv = self.recv[theory_len * 2:]
                    # print('截取后',self.recv)
                    cmd_start = cmd[0:2]
                    cmd_len = cmd[2:4]
                    cmd_sum = cmd[4:6]
                    if cmd_sum == (hex(int(cmd_start, 16) + int(cmd_len, 16))).upper()[-2:]:
                        cmd_cmd = cmd[6:8]
                        cmd_data = cmd[8:-2]
                        cmd_endsum = cmd[-2:]
                        s = 0
                        for i in range(0, len(cmd) - 2):
                            if i % 2 == 0:
                                s = s + int(cmd[i:i + 2], 16)
                        s = hex(s).upper()[-2:]
                        if s == cmd_endsum:
                            try:
                                self.cmd_type(cmd_cmd, cmd_data)
                            except Exception as e:
                                print(e)
                        else:
                            if DEBUG:
                                self.recv = ''
                                print('校验错误！！！！！！！！')
                                print('正确：', s, '错误:', cmd_endsum)
                else:
                    return
        else:
            self.recv = self.recv[self.recv.find('AA'):]

    def cmd_type(self, cmd_cmd, cmd_data):
        pos = (self.real_x,self.real_y,self.real_z)
        # flash读写基本配置
        self.baseSetWidget.serial_deal(cmd_cmd, cmd_data)
        # 重复性测量
        #  self.repeatMeasureDlg.serial_deal(cmd_cmd, cmd_data, pos)
        # 漏射线测量
        self.envMeasureWidget.serial_deal(cmd_cmd, cmd_data)
        if cmd_cmd == '51':
            self.doneMoveToTarget(cmd_data)
        if cmd_cmd == '50':
            self.doneOnceMeasure(cmd_data)  # 连续走位
        if cmd_cmd == '5A':
            self.doneCurrentTask()
        if cmd_cmd == '44':  # 复位坐标
            self.real_x = 0
            self.real_y = 0
            self.real_z = 0
            self.pre_x = 0
            self.pre_y = 0
            self.pre_z = 0
            self.signalOpengl.emit(0, 0, 0)
            self.signalMsg.emit('提示', '坐标复位成功')
        if cmd_cmd == '53':  # 读取坐标
            data = cmd_data[2:]
            self.real_x, self.real_y, self.real_z = self.get_xyz(data)
            self.signalOpengl.emit(self.real_x, self.real_y, self.real_z)
        if cmd_cmd == '54':  # 紧急停止
            self.doneStop(cmd_data)
        if cmd_cmd == '55':  # 紧急停止
            # 清除命令任务执行标志
            self.taskRunThread.clearFlag()
            data = cmd_data[2:]
            x = data[2:10]
            y = data[12:20]
            z = data[22:30]
            self.updateAxis(x,y,z)
        if cmd_cmd == '80':
            edge = cmd_data[0:2]
            data = cmd_data[2:]
            x = data[2:10]
            y = data[12:20]
            z = data[22:30]
            self.updateAxis(x,y,z)
            inf = '达到限位'
            if edge == '31':
                inf = 'x+向达到限位'
            if edge == '32':
                inf = 'x-向达到限位'
            if edge == '33':
                inf = 'y+向达到限位'
            if edge == '34':
                inf = 'y-向达到限位'
            if edge == '35':
                inf = 'z+向达到限位'
            if edge == '36':
                inf = 'z-向达到限位'
            box = QMessageBox(QMessageBox.Information, "提示", "%s!\r\n\r\n是否回到原点?"%inf)
            qyes = box.addButton(self.tr("回原点"), QMessageBox.YesRole)
            qcancel = box.addButton(self.tr("取消"), QMessageBox.NoRole)
            box.exec_()
            if box.clickedButton() == qyes:
                self.taskRunThread.clearFlag()
                self.cmd_backZero()
            elif box.clickedButton() == qcancel:
                self.taskRunThread.clearFlag()
            else:
                return
        if cmd_cmd == '6B':
            data = cmd_data[2:]
            limit_x1 = slice(2,10)
            limit_x2 = slice(12,20)
            limit_y1 = slice(22,30)
            limit_y2 = slice(32,40)
            limit_z1 = slice(42,50)
            limit_z2 = slice(52,60)
            self.motorControlWidget.updateLimit(
                self.HexToNeg(data[limit_x1])/AXIS_MULTIPLE ,
                self.HexToNeg(data[limit_x2])/AXIS_MULTIPLE ,
                self.HexToNeg(data[limit_y1])/AXIS_MULTIPLE ,
                self.HexToNeg(data[limit_y2])/AXIS_MULTIPLE ,
                self.HexToNeg(data[limit_z1])/AXIS_MULTIPLE ,
                self.HexToNeg(data[limit_z2])/AXIS_MULTIPLE
            )
        if cmd_cmd == '6A':
            QMessageBox.information(self, '提示', '设置限位成功！', QMessageBox.Yes)
        if cmd_cmd == '71': # 重复性测量
            data = cmd_data
            if self.repeatMeasureDlg.measure_flag:
                '''调试测量数据'''
                t1 = 0
                t2 = 0
                if data[:2] == 'FF':
                    t1 = int(data[:8], 16) - int('FFFFFFFF', 16) - 1
                else:
                    t1 = int(data[:8], 16)
                if data[-8:-6] == 'FF':
                    t2 = int(data[-8:], 16) - int('FFFFFFFF', 16) - 1
                else:
                    t2 = int(data[-8:], 16)
                t1 = self.baseSetWidget.scale_k*t1+self.baseSetWidget.scale_b
                t2 = self.baseSetWidget.scale_k_2*t2+self.baseSetWidget.scale_b_2
                x = pos[0]
                y = pos[1]
                z = pos[2]
                self.repeatMeasureDlg.table_add_data(x=x,y=y,z=z, t1=t1, t2=t2)
        if cmd_cmd == '73':  # 停止测量
            pass
        if cmd_cmd == '41':  # 停止测量
            self.repeatMeasureDlg.MessageBox('提示', '设置传输速度成功！')
            # QMessageBox.information(self, '提示', '设置传输速度成功！', QMessageBox.Yes)
        if cmd_cmd == '82':
            # 读取信息
            _len = int(cmd_data[0:2], 16)
            # flash信息处理
            flash_start = 2
            flash_end = 2+_len*2
            flash_info = cmd_data[flash_start:flash_end]
            self.baseSetWidget.updateRead(flash_info)
            # 坐标信息处理
            axis_start = flash_end
            axis_end = flash_end + 3*4*2
            axis_info = cmd_data[axis_start:axis_end]

            self.real_x = self.HexToNeg(axis_info[0:8])/AXIS_MULTIPLE
            self.real_y = self.HexToNeg(axis_info[8:16])/AXIS_MULTIPLE
            self.real_z = self.HexToNeg(axis_info[16:24])/AXIS_MULTIPLE
            self.signalOpengl.emit(self.real_x, self.real_y, self.real_z)
            # 摆放位置
            angle_start = axis_end
            angle_end = axis_end + 2
            angle_info = cmd_data[angle_start:angle_end]
            self.proj_dock.treeWidget.pddDlg.styleSet(angle_info)
            self.proj_dock.treeWidget.oarDlg.styleSet(angle_info)
            QMessageBox.information(self, '提示', '下位机信息读取成功！', QMessageBox.Yes)
            self.actionEnabled(['start','env','repeat'])

    def updateAxis(self,x,y,z):
        """更新坐标"""
        def HexToNeg(hex_str):
            if hex_str[0] == 'F' or hex_str[0] == 'f':
                return int(hex_str, 16) - (1 << 32)
            else:
                return int(hex_str, 16)
        x = HexToNeg(x)
        y = HexToNeg(y)
        z = HexToNeg(z)
        self.real_x = float('%.3f' % (x / 100))
        self.real_y = float('%.3f' % (y / 100))
        self.real_z = float('%.3f' % (z / 100))
        # self.real_x = float('%.3f' % (x / AXIS_MULTIPLE))
        # self.real_y = float('%.3f' % (y / AXIS_MULTIPLE))
        # self.real_z = float('%.3f' % (z / AXIS_MULTIPLE))
        self.signalOpengl.emit(self.real_x, self.real_y, self.real_z)

    def get_xyz(self, data):
        '''处理数据包中包含的xyz坐标'''
        if data[2:4] == 'FF':  # 计算通道x
            x = int(data[2:10], 16) - int('FFFFFFFF', 16) - 1
        else:
            x = int(data[2:10], 16)
        if data[12:14] == 'FF':  # 计算通道y
            y = int(data[12:20], 16) - int('FFFFFFFF', 16) - 1
        else:
            y = int(data[12:20], 16)
        if data[22:24] == 'FF':  # 计算通道z
            z = int(data[22:30], 16) - int('FFFFFFFF', 16) - 1
        else:
            z = int(data[22:30], 16)
        return x / AXIS_MULTIPLE, y / AXIS_MULTIPLE, z / AXIS_MULTIPLE

    def cmd_read_axis(self):
        '''读取当前坐标坐标'''
        cmd = WATER_CMD['读取坐标']
        self.cmd_send(cmd)

    def cmd_ResetAxis(self):
        '''复位坐标系'''
        cmd = WATER_CMD['复位坐标']
        cmd[4] = ''
        self.cmd_send(cmd)

    def cmd_MoveStop(self):
        '''紧急停止'''
        cmd = WATER_CMD['移动停止']
        cmd[4] = ''
        self.cmd_send(cmd)
        # self.back_zero_flag = False
        # 开启移动标志
        self.move_flag = False
        self.stop_flag = True
        # self.road_cnt= 0
        self.task_flag = False

    def cmd_ReadInfo(self):
        cmd = WATER_CMD['读取信息']
        cmd[4] = '18'
        self.cmd_send(cmd)

    def motorMoveAxis_position(self, x, y, z):
        self.err_x = float(x) - self.real_x
        self.err_y = float(y) - self.real_y
        self.err_z = float(z) - self.real_z
        # self.move_flag = True
        if self.err_x < 0:
            cmd_x = ('32')
        else:
            cmd_x = ('31')
        if self.err_y < 0:
            cmd_y = ('34')
        else:
            cmd_y = ('33')
        if self.err_z < 0:
            cmd_z = ('36')
        else:
            cmd_z = ('35')
        if DEBUG:
            print(('电机走步差值 x:%s y:%s z:%s' % (self.err_x, self.err_y, self.err_z)))
            print('电机正在移动到坐标:')
            print(('(%s,%s,%s)' % (x, y, z)))
        cmd = WATER_CMD['电机走步']
        data_x = hex(abs(int('%.0f'%(self.err_x * AXIS_MULTIPLE)))).replace('0x', '').zfill(8).upper()
        data_y = hex(abs(int('%.0f'%(self.err_y * AXIS_MULTIPLE)))).replace('0x', '').zfill(8).upper()
        data_z = hex(abs(int('%.0f'%(self.err_z * AXIS_MULTIPLE)))).replace('0x', '').zfill(8).upper()
        # data_y = hex(int(abs(round(self.err_y, 2) * AXIS_MULTIPLE))).replace('0x', '').zfill(8).upper()
        # data_z = hex(int(abs(round(self.err_z, 2) * AXIS_MULTIPLE))).replace('0x', '').zfill(8).upper()
        cmd[4] = cmd_x + data_x + cmd_y + data_y + cmd_z + data_z
        self.cmd_send(cmd)

    def motorMoveAxis_continue(self, x, y, z, step_x, step_y, step_z):
        self.err_x = float(x) - self.real_x
        self.err_y = float(y) - self.real_y
        self.err_z = float(z) - self.real_z
        # self.move_flag = True
        if self.err_x < 0:
            cmd_x = ('32')
        else:
            cmd_x = ('31')
        if self.err_y < 0:
            cmd_y = ('34')
        else:
            cmd_y = ('33')
        if self.err_z < 0:
            cmd_z = ('36')
        else:
            cmd_z = ('35')
        if DEBUG:
            print(('电机走步差值 x:%s y:%s z:%s' % (self.err_x, self.err_y, self.err_z)))
            print('电机正在移动到坐标:')
            print(('(%s,%s,%s)' % (x, y, z)))
        cmd = WATER_CMD['连续测量']
        data_x = hex(int(abs(self.err_x * AXIS_MULTIPLE))).replace('0x', '').zfill(8).upper()
        data_y = hex(int(abs(self.err_y * AXIS_MULTIPLE))).replace('0x', '').zfill(8).upper()
        data_z = hex(int(abs(self.err_z * AXIS_MULTIPLE))).replace('0x', '').zfill(8).upper()
        step_pulse_x = hex(int(abs(step_x * AXIS_MULTIPLE))).replace('0x', '').zfill(8).upper()
        step_pulse_y = hex(int(abs(step_y * AXIS_MULTIPLE))).replace('0x', '').zfill(8).upper()
        step_pulse_z = hex(int(abs(step_z * AXIS_MULTIPLE))).replace('0x', '').zfill(8).upper()
        cmd[4] = cmd_x + data_x + cmd_y + data_y + cmd_z + data_z + \
                 '3A' + step_pulse_x + '3B' + step_pulse_y + '3C' + step_pulse_z
        self.cmd_send(cmd)

    def setLimit(self, x1,x2,y1,y2,z1,z2):
        # err_x = self.real_x
        # err_y = self.real_y
        # err_z = self.real_z
        def tohex_8(t):
            return hex(0xFFFFFFFF&int(t*AXIS_MULTIPLE)).replace('0x', '').zfill(8).upper()
        cmd = WATER_CMD['设置限位']
        cmd[4] ='31' + tohex_8(x1) +\
                '32' + tohex_8(x2) +\
                '33' + tohex_8(y1) +\
                '34' + tohex_8(y2) +\
                '35' + tohex_8(z1) +\
                '36' + tohex_8(z2)
        self.cmd_send(cmd)

    def get_road_axis(self, road):
        '''获取任务路线所有坐标'''
        road = []

    def graphChange(self):
        "图表切换"
        if self.task_current['type'] == 'SCAN':
            self.graph.setVisible(False)
            self.graphScan.setVisible(True)
        else:
            self.graph.setVisible(True)
            self.graphScan.setVisible(False)
        self.scanPoint_x = np.array([])
        self.scanPoint_y = np.array([])
        self.scanPoint_z = np.array([])

    def taskRunInit(self):
        "开始移动控制线程"
        # 创建移动控制线程
        self.taskRunThread = TaskRunManager()
        self.taskRunThread.signal.connect(self.MoveControl)

        self.timeTaskRun = QTimer()
        self.timeTaskRun.timeout.connect(self.taskRunThread.running)
        self.timeTaskRun.start(100)

        self.moveControlThread_done = False

    def taskRunFinish(self):
        "关闭移动控制线程"
        self.taskRunThread.done = True
        self.moveControlThread_done = True

    def startMove(self):
        self.task = self.proj_dock.treeWidget.getSelectTasks()
        if self.task:
            self.taskRunThread.firstRun()
        else:
            QMessageBox.warning(self, "提示","请选择要执行的任务！", QMessageBox.Yes)

    def pauseMove(self):
        if self.task:
            self.taskRunThread.pauseRun()
        else:
            QMessageBox.warning(self, "提示","请选择要执行的任务！", QMessageBox.Yes)

    def doneCurrentTask(self):
        """执行完当前任务, 命令:0x5A"""
        self.proj_dock.treeWidget.addDataFile(condition=self.task_current, data=self.pddoarList)

        self.actionDisable(['stop'])
        if self.task_cnt >= len(self.task) - 1:
            self.proj_dock.treeWidget.markDoneTaskFile(self.taskRunThread.step)
            "所有任务执行完成"
            self.task_flag = False
            self.move_flag = False
            self.measure_flag = False
            self.task_cnt = 0
            # 设置移动到第一个点标记
            self.task_move_first_flag = False
            self.progress_value = 0
            # 清除线程中的步骤
            self.taskRunThread.step = 0

            box = QMessageBox(QMessageBox.Information, "提示", "所有任务已经执行完成!\r\n\r\n是否回到原点?")
            qyes = box.addButton(self.tr("回原点"), QMessageBox.YesRole)
            qcancel = box.addButton(self.tr("取消"), QMessageBox.NoRole)
            box.exec_()
            if box.clickedButton() == qyes:
                self.taskRunThread.clearFlag()
                self.cmd_backZero()
            elif box.clickedButton() == qcancel:
                self.taskRunThread.clearFlag()
                self.startTaskAct.setEnabled(True)
            else:
                return
        else:
            self.proj_dock.treeWidget.markDoneTaskFile(self.taskRunThread.step)
            if self.baseSetWidget.task_auto:
                self.taskRunThread.nextRun()
            else:
                box = QMessageBox(QMessageBox.Information, "提示", "是否执行下一个任务?")
                qyes = box.addButton(self.tr("执行"), QMessageBox.YesRole)
                qno = box.addButton(self.tr("回原点"), QMessageBox.NoRole)
                qcancel = box.addButton(self.tr("取消"), QMessageBox.NoRole)
                box.exec_()
                if box.clickedButton() == qyes:
                    self.taskRunThread.nextRun()
                elif box.clickedButton() == qno:
                    self.taskRunThread.clearFlag()
                    self.cmd_backZero()
                elif box.clickedButton() == qcancel:
                    self.taskRunThread.clearFlag()
                    self.startTaskAct.setEnabled(True)
                else:
                    return

    def doneMoveToTarget(self, data):
        """移动到目标点走位完成, 命令：0x51"""
        if data == 'FF':  # 走位完成
            self.signalOpengl.emit(self.real_x, self.real_y, self.real_z)
            if self.taskRunThread.move_to_first_flag:
                self.taskRunThread.continueRun()
                self.taskRunThread.move_to_first_flag = False
                return
            if self.real_x==0 and self.real_y == 0 and self.real_z == 0:
                QMessageBox.information(self, '提升','已经回到原点！',QMessageBox.Yes)
                self.startTaskAct.setEnabled(True)
            return
        data = data[2:]
        x = self.HexToNeg(data[2:10])
        y = self.HexToNeg(data[12:20])
        z = self.HexToNeg(data[22:30])
        self.real_x = x / AXIS_MULTIPLE
        self.real_y = y / AXIS_MULTIPLE
        self.real_z = z / AXIS_MULTIPLE
        self.signalOpengl.emit(self.real_x, self.real_y, self.real_z)

    def doneOnceMeasure(self, data):
        """完成一次数据测量采集, 命令: 0x50"""
        # print("数据包",data)
        if data == 'FF':  # 走步任务完成
            print("连续测量任务完成")
            return
        else:
            data = data[2:]

            x = self.HexToNeg(data[2:10])
            y = self.HexToNeg(data[12:20])
            z = self.HexToNeg(data[22:30])
            self.real_x = float('%.3f' % (x / AXIS_MULTIPLE))
            self.real_y = float('%.3f' % (y / AXIS_MULTIPLE))
            self.real_z = float('%.3f' % (z / AXIS_MULTIPLE))
            self.signalOpengl.emit(self.real_x, self.real_y, self.real_z)
            self.progress_value += 1

            t1 = self.HexToNeg(data[-16:-8])
            t2 = self.HexToNeg(data[-8:])

            # ad采集饱和溢出, 紧急停止
            if t1 > 60000 or t2 > 60000:
                self.pauseMove()
                return

            # pdd_data = Help.pdd_data
            # # 模拟数据
            # for i in range(0, len(pdd_data)):
                # tmp_z = pdd_data[i][0]
                # if z==tmp_z*100:
                    # t2 = pdd_data[i][1]
                    # t1 = pdd_data[i][2]
            #         break

            # 刻度系数转换
            t1 = self.baseSetWidget.scale_k*t1+self.baseSetWidget.scale_b
            t2 = self.baseSetWidget.scale_k_2*t2+self.baseSetWidget.scale_b_2

            t1 = float('%.4f'%t1)
            t2 = float('%.4f'%t2)

            t = self.task_current
            # 添加一次数据
            timetest = time.strftime("%Y:%m:%d-%H:%M:%S", time.localtime())
            if t['ray_type'] == '电子':
                power = t['power'].replace('MV','').replace('MeV','')
                if int(power) < 10:
                    self.pddoarList.append([self.real_x, self.real_y, self.real_z, t2, t1])
                    self.dataTab.add_data(x=self.real_x, y=self.real_y, z=self.real_z, t1=t2, t2=t1)
                else:
                    self.pddoarList.append([self.real_x, self.real_y, self.real_z, t1, t2])
                    self.dataTab.add_data(x=self.real_x, y=self.real_y, z=self.real_z, t1=t1, t2=t2)
                #  self.dataTab.insert_data(rowNum=len(self.pddoarList),x=self.real_x, y=self.real_y, z=self.real_z, t1=t2, t2=t1)
            else:
                self.pddoarList.append([self.real_x, self.real_y, self.real_z, t1, t2])
                self.dataTab.add_data(x=self.real_x, y=self.real_y, z=self.real_z, t1=t1, t2=t2)
                #  self.dataTab.insert_data(rowNum=len(self.pddoarList), x=self.real_x, y=self.real_y, z=self.real_z, t1=t1, t2=t2)
            # return
            # 加速器异常停止
            if len(self.pddoarList) > 1:
                # task_stop_err = self.baseSetWidget.scale_k_2*self.baseSetWidget.task_stop_err+self.baseSetWidget.scale_b_2
                err = abs(self.pddoarList[-2][-1] - self.pddoarList[-1][-1])
                task_stop_err = self.baseSetWidget.task_stop_err
                if abs(self.pddoarList[-2][-1] - self.pddoarList[-1][-1]) > task_stop_err:
                    self.pauseMove()
                    return

            if self.measure_flag:
                data = self.pddoarList
                # 曲线显示数据是否实时修正
                task_show = self.baseSetWidget.task_show
                if task_show == "modify":
                    x = [float(d[0]) for d in data]
                    y = [float(d[1]) for d in data]
                    z = [float(d[2]) for d in data]
                    tm = [float('%.2f'%float(d[3])) for d in data]
                    ts = [float('%.2f'%float(d[4])) for d in data]
                    repeat  = [float('%.4f'%(m/(s+0.00000001))) for m,s in zip(tm,ts)]
                    normal = [float('%.4f'%(100*r/(max(repeat)+0.0000001))) for r in repeat]
                    data = []
                    for i in range(0, len(normal)):
                        # data.append([x[i], y[i], z[i], normal[i], ts[i]])
                        data.append([x[i], y[i], z[i], normal[i], 0])

                if t['type'] == 'PDD':
                    self.signalTunnel.emit([d[2] for d in data], [d[3] for d in data], [d[4] for d in data])
                    return
                if t['type'] == 'OAR':
                    if t['angle'] == '0' or t['angle'] == '180':
                        if t['direction'] == 'G->T' or t['direction'] == 'T->G':
                            self.signalTunnel.emit([d[1] for d in data], [d[3] for d in data], [d[4] for d in data])
                        else:
                            self.signalTunnel.emit([d[0] for d in data], [d[3] for d in data], [d[4] for d in data])
                    if t['angle'] == '90' or t['angle'] == '270':
                        if t['direction'] == 'G->T' or t['direction'] == 'T->G':
                            self.signalTunnel.emit([d[0] for d in data], [d[3] for d in data], [d[4] for d in data])
                        else:
                            self.signalTunnel.emit([d[1] for d in data], [d[3] for d in data], [d[4] for d in data])

    def doneStop(self, data):
        """ 完成紧急停止, 命令: 0x54"""
        if len(self.pddoarList) > 1:
            print("删除多余的数")
            del self.pddoarList[-1]
            del self.pddoarList[-1]
            row = self.dataTab.model.rowCount()
            if row >= 2:
                self.dataTab.model.removeRow(row-1)
                self.dataTab.model.removeRow(row-2)
            else:
                self.dataTab.model.removeRow(0)
        else:
            self.dataTab.clear_data()
            self.pddoarList = []
        data = data[2:]
        x = self.HexToNeg(data[2:10])
        y = self.HexToNeg(data[12:20])
        z = self.HexToNeg(data[22:30])
        self.real_x = float('%.3f' % (x / AXIS_MULTIPLE))
        self.real_y = float('%.3f' % (y / AXIS_MULTIPLE))
        self.real_z = float('%.3f' % (z / AXIS_MULTIPLE))
        self.signalOpengl.emit(self.real_x, self.real_y, self.real_z)
        box = QMessageBox(QMessageBox.Information, "警告", "任务执行中断: \r\n1、加速器异常停止\r\n2、剂量率过大，数据溢出\r\n是否继续执行任务?")
        qyes = box.addButton(self.tr("继续执行"), QMessageBox.YesRole)
        qno = box.addButton(self.tr("回原点"), QMessageBox.NoRole)
        qcancel = box.addButton(self.tr("取消"), QMessageBox.NoRole)
        box.exec_()
        if box.clickedButton() == qyes:
            self.taskRunThread.continueRun()
        elif box.clickedButton() == qno:
            self.taskRunThread.clearFlag()
            self.cmd_backZero()
        elif box.clickedButton() == qcancel:
            self.taskRunThread.clearFlag()
            self.startTaskAct.setEnabled(True)
        else:
            return

    def MoveControl(self, msg):
        # print(">>>接收到处理指令:", msg)
        # 处理消息
        if msg == "CONTINUE":  # 执行当前任务
            self.task_cnt = self.taskRunThread.step
            self.task_current = self.task[self.taskRunThread.step]
            if self.taskRunThread.pause_flag and self.progress_value <= 0:
                self.taskRunThread.moveToFirstPos()
                # 撤销暂停标记
                self.taskRunThread.pause_flag = False
            else:
                self.cmd_RunTask(self.task_current)
                self.proj_dock.treeWidget.markRunTaskFile(self.taskRunThread.step)
                self.signalOpenglLoadConfig.emit(self.task_current)
        if msg == "FIRST":  # 从指定位置开始移动
            self.taskRunThread.step = 0
            self.taskRunThread.moveToFirstPos()
            self.startTaskAct.setEnabled(False)
        if msg == "SELECT":  # 从指定位置开始移动
            # self.taskRunThread.step = self.proj_dock.taskTableView.select_row
            self.taskRunThread.moveToFirstPos()
            self.dataTab.clear_data()
        if msg == "PAUSE":  # 暂停
            self.proj_dock.treeWidget.markStopTaskFile(self.taskRunThread.step)
            if self.progress_value >= 2:
                self.progress_value -= 2
            else:
                self.progress_value = 0
            self.cmd_MoveStop()
        if msg == "NEXT":  # 下一任务
            self.taskRunThread.moveToFirstPos()
        if msg == "MOVE_FIRST":  # 移动到第一个点位置
            self.stopTaskAct.setEnabled(True)
            self.task_current = self.task[self.taskRunThread.step]
            self.task_cnt = self.taskRunThread.step
            t = self.task_current
            self.motorMoveAxis_position(t['road'][0][0], t['road'][0][1], t['road'][0][2])
            self.progress_value = 0
            self.measure_flag = True
            self.pddoarList = []
            self.dataTab.clear_data()
            #  self.proj_dock.taskTableView.showCurrentTask(self.task_cnt)
            # 曲线固定x轴范围
            if t['type'] == 'PDD':
                x_list = [x[2] for x in t['road']]
                self.graph.plot.setLimits(xMin=min(x_list), xMax=max(x_list), yMin=0, yMax=110)
            if t['type'] == 'OAR':
                x_list = []
                if t['angle'] == '0' or t['angle'] == '180':
                    if t['direction'] == 'G->T' or t['direction'] == 'T->G':
                        x_list = [x[1] for x in t['road']]
                    else:
                        x_list = [x[0] for x in t['road']]
                if t['angle'] == '90' or t['angle'] == '270':
                    if t['direction'] == 'G->T' or t['direction'] == 'T->G':
                        x_list = [x[0] for x in t['road']]
                    else:
                        x_list = [x[1] for x in t['road']]
                if x_list:
                    self.graph.plot.setLimits(xMin=min(x_list) - 10, xMax=max(x_list) + 10, yMin=0, yMax=110)
                else:
                    self.graph.plot.setLimits(xMin=-200, xMax=200, yMin=0, yMax=110)

    def HexToNeg(self, hex_str):
        if hex_str[0] == 'F' or hex_str[0] == 'f':
            return int(hex_str, 16) - (1 << 32)
        else:
            return int(hex_str, 16)

    def cmd_RunTask(self, task):
        '''发送执行任务命令'''
        road = task['road']
        # 将负数转换成十六进制
        tohex = lambda val, nbits: hex((val + (1 << nbits)) % (1 << nbits))
        cmd_axis = hex(int(abs(len(road)))).replace('0x', '').zfill(4).upper()
        for a in road:
            data_x = tohex(int('%.0f'%(a[0] * AXIS_MULTIPLE)), 16).replace('0x', '').zfill(4).upper()
            data_y = tohex(int('%.0f'%(a[1] * AXIS_MULTIPLE)), 16).replace('0x', '').zfill(4).upper()
            data_z = tohex(int('%.0f'%(a[2] * AXIS_MULTIPLE)), 16).replace('0x', '').zfill(4).upper()
            cmd_axis += data_x + data_y + data_z
        cmd = WATER_CMD['任务坐标']
        cmd[4] = cmd_axis
        logger.info('任务坐标')
        logger.info(road)
        #  if DEBUG:
            #  print('发送任务坐标', road)
        self.cmd_send(cmd)
        self.signalTunnel.emit([], [], [])
        self.move_dock.rayGL.load_config(task)
        self.startTaskAct.setEnabled(False)
        # self.dataTab.clear_data()

    def cmd_backZero(self):
        '''回到原点'''
        # self.task_move_first_flag = False
        # self.taskRunThread.clearFlag()
        self.motorMoveAxis_position(0, 0, 0)

    def MessageBox(self, title='',msg=''):
        QMessageBox.information(self, title ,msg ,QMessageBox.Yes)


if __name__ == '__main__':
    import sys

    app = QApplication(sys.argv)
    # win = DeviceInputDlg()
    # win = ScanDialog()
    win = MainWindow()
    # win = TaskListWidget()
    # win = PddDialog()
    # win = OarDialog()
    win.show()
    app.exec_()


